#pragma once  

#include <iostream>    
#include <vector>       // 提供“std::vector容器” ---> 作为环形队列的底层存储结构
#include "Sem.hpp"      // 包含“自定义Sem信号量类头文件” ---> 用于控制生产/消费的同步

//1.环形队列的默认容量
static const int gcap = 5; 

//2.使用SemModule命名空间，简化Sem信号量类的调用
using namespace SemModule;  


// 模板类：支持存储任意类型T的数据，实现线程安全的环形队列（适配多生产者-多消费者模型）
template <typename T>
class RingQueue
{
private:
    /*----------------------底层存储容器----------------------*/
    //1.定义vector容器实现的环形缓冲区（固定大小_cap）
    //2.环形队列的最大容量（缓冲区大小）
    std::vector<T> _rq;  
    int _cap;           

    /*----------------------生产者相关资源：控制生产者的同步和互斥----------------------*/
    //3.空位置信号量 ---> 记录队列中空闲位置的数量，控制生产者是否可入队
    //4.生产者下标 ---> 标记下一个要写入数据的位置（0 ~ _cap-1循环）
    Sem _blank_sem;
    int _p_step;    

    /*----------------------消费者相关资源：控制消费者的同步和互斥----------------------*/
    //4.数据信号量 ---> 记录队列中可用数据的数量，控制消费者是否可出队
    //5.消费者下标 ---> 标记下一个要读取数据的位置（0 ~ _cap-1循环）
    Sem _data_sem; 
    int _c_step;   


public:
    //1.“构造函数” ---> 初始化环形队列的容量、存储容器、信号量和下标
    RingQueue(int cap = gcap) //cap - 队列容量，默认值为gcap（5）
        :_rq(cap),        // 初始化底层存储vector，大小为cap（环形缓冲区）
        _cap(cap),        // 初始化队列最大容量
        
        _blank_sem(cap),  // 初始化"空位置"信号量，初始值=cap（队列初始全为空）
        _p_step(0),       // 生产者下标：记录下一个生产位置，初始化为0
          
        _data_sem(0),     // 初始化"数据"信号量，初始值=0（队列初始无数据）
        _c_step(0)        // 消费者下标：记录下一个消费位置，初始化为0
    {
    }

    //2.入队操作（生产者调用）---> 将数据写入环形队列，线程安全支持多生产者
    void Equeue(const T &in) //参数：in - 要入队的数据（const T& 避免拷贝，保证数据不可修改）
    {
        //1.申请"空位置"信号量（P操作）：
        _blank_sem.P();
        /* 生产者P操作的说明：
        *     1. 若有空闲位置（信号量>0），申请成功，信号量值-1
        *     2. 若队列已满（信号量=0），生产者线程阻塞等待，直到有消费者释放空位置
        */

        //2.生产数据 ---> 将输入数据写入当前生产者下标对应的位置
        _rq[_p_step] = in;

        //3.更新生产者下标 ---> 指向 next 生产位置
        ++_p_step;

        //4.维持环形特性 ---> 当下标达到容量上限时，取模重置为0（循环复用缓冲区）
        _p_step %= _cap;

        //5.发送"数据"信号量（V操作）
        _data_sem.V();
        /* 生产者V操作的说明：
        *      1. 数据入队后，可用数据数+1，信号量值+1
        *      2. 唤醒正在等待"数据"的消费者线程（若有）
        */
    }

    //3.出队操作（消费者调用）---> 从环形队列读取数据，线程安全支持多消费者
    void Pop(T *out) //参数：out - 输出参数（指针），用于存储读出的数据（通过指针修改外部变量）
    {
        //1.申请"数据"信号量（P操作）
        _data_sem.P();
        /* 消费者P操作的说明：
        *     1. 若有可用数据（信号量>0），申请成功，信号量值-1
        *     2. 若队列为空（信号量=0），消费者线程阻塞等待，直到有生产者写入数据
        */

        //2.消费数据 ---> 将当前消费者下标对应的位置的数据写入out指针
        *out = _rq[_c_step];

        //3.更新消费者下标 ---> 指向 next 消费位置
        ++_c_step;

        //4.维持环形特性 ---> 当下标达到容量上限时，取模重置为0（循环复用缓冲区）
        _c_step %= _cap;

        //5. 发送"空位置"信号量（V操作）
        _blank_sem.V();
        /* 消费者V操作的说明：
        *      1. 数据出队后，空闲位置数+1，信号量值+1
        *      2. 唤醒正在等待"空位置"的生产者线程（若有）
        */
    }


};